package com.example.lotterysystem.service.impl;

import com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.util.JacksonUtil;
import com.example.lotterysystem.common.util.RedisUtil;
import com.example.lotterysystem.controller.param.CreatePrizeByActivityParam;
import com.example.lotterysystem.controller.param.CreateUserByActivityParam;
import com.example.lotterysystem.controller.param.PageParam;
import com.example.lotterysystem.controller.param.CreateActivityParam;
import com.example.lotterysystem.dao.dataObject.ActivityDO;
import com.example.lotterysystem.dao.dataObject.ActivityPrizeDO;
import com.example.lotterysystem.dao.dataObject.ActivityUserDO;
import com.example.lotterysystem.dao.dataObject.PrizeDO;
import com.example.lotterysystem.dao.mapper.*;
import com.example.lotterysystem.service.ActivityService;
import com.example.lotterysystem.service.dto.ActivityDTO;
import com.example.lotterysystem.service.dto.ActivityDetailDTO;
import com.example.lotterysystem.service.dto.CreateActivityDTO;
import com.example.lotterysystem.service.dto.PageListDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class ActivityServiceImpl implements ActivityService {
    private static final Logger logger = LoggerFactory.getLogger(ActivityServiceImpl.class);
    /**
     * 活动缓存前缀(区分活动)
     */
    private final String ACTIVITY_PREFIX="ACTIVITY_";
    /**
     * 活动缓存时间
     */
    private final Long ACTIVITY_TIMEOUT = 60*60*24*3L;

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PrizeMapper prizeMapper;
    @Autowired
    private ActivityMapper activityMapper;
    @Autowired
    private ActivityUserMapper activityUserMapper;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private RedisUtil redisUtil;

    @Override
    @Transactional(rollbackFor = Exception.class)//当遇到异常时就进行回滚,因为涉及到多表,所以需要保证表内信息的统一
    public CreateActivityDTO createActivity(CreateActivityParam param) {
        //校验活动信息是否正确
        checkActivityInfo(param);
        //保存活动信息(活动本身的基本信息)
        ActivityDO activityDO = new ActivityDO();
        activityDO.setActivityName(param.getActivityName());
        activityDO.setDescription(param.getDescription());
        activityDO.setStatus(ActivityStatusEnum.RUNNING.name());
        activityMapper.insert(activityDO);


        //保存活动关联的奖品信息和人员信息
        //奖品信息
        List<CreatePrizeByActivityParam> prizeParams = param.getActivityPrizeList();
        List<ActivityPrizeDO> activityPrizeDOList = prizeParams.stream()
                .map(prizeParam->{
                    ActivityPrizeDO activityPrizeDO = new ActivityPrizeDO();
                    activityPrizeDO.setActivityId(activityDO.getId());
                    activityPrizeDO.setPrizeId(prizeParam.getPrizeId());
                    activityPrizeDO.setPrizeAmount(prizeParam.getPrizeAmount());
                    activityPrizeDO.setPrizeTiers(prizeParam.getPrizeTiers());
                    activityPrizeDO.setStatus(ActivityPrizeStatusEnum.INIT.name());
                    return activityPrizeDO;
                })
                .toList();
        activityPrizeMapper.batchInsert(activityPrizeDOList);//批量设置奖品关联信息

        //人员信息
        List<CreateUserByActivityParam> userParams = param.getActivityUserList();
        List<ActivityUserDO> activityUserDOList = userParams.stream()
                .map(userParam ->{
                    ActivityUserDO activityUserDO = new ActivityUserDO();
                    activityUserDO.setActivityId(activityDO.getId());
                    activityUserDO.setUserId(userParam.getUserId());
                    activityUserDO.setUserName(userParam.getUserName());
                    activityUserDO.setStatus(ActivityUserStatusEnum.INIT.name());
                    return activityUserDO;
                })
                .toList();
        activityUserMapper.batchInsert(activityUserDOList);

        //整合完整的活动信息,存放redis
        //ActivityDetailInfo : 活动+奖品+人员信息

        //先获取奖品的基本属性
        //获取奖品需要查询的奖品id
        List<Long> prizeIds = param.getActivityPrizeList().stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()
                .toList();
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);

        ActivityDetailDTO activityDetailDTO = convertToActivityDetailDTO(activityDO,activityUserDOList
                ,prizeDOList,activityPrizeDOList);
        cacheActivity(activityDetailDTO);
        //构造返回
        CreateActivityDTO createActivityDTO = new CreateActivityDTO();
        createActivityDTO.setActivityId(activityDO.getId());
        return createActivityDTO;
    }

    @Override
    public PageListDTO<ActivityDTO> findActivityList(PageParam param) {
        //获取总量
        int count = activityMapper.count();

        //获取当前页
        List<ActivityDO> activityDOList = activityMapper.selectActivityList(param.offset(),param.getPageSize());
        List<ActivityDTO> activityDTOList = activityDOList
                .stream()
                .map(activityDO -> {
                    ActivityDTO activityDTO = new ActivityDTO();
                    activityDTO.setActivityId(activityDO.getId());
                    activityDTO.setActivityName(activityDO.getActivityName());
                    activityDTO.setDescription(activityDO.getDescription());
                    activityDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));
                    return activityDTO;
                }).toList();
        return new PageListDTO<>(count,activityDTOList);
    }

    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        if(null== activityId){
            logger.warn("查询活动信息的activityId为空,查询失败!");
            return null;
        }
        //查询 redis
        ActivityDetailDTO activityDetailDTO = getActivityFromCache(activityId);
        System.out.println("activityDetailDTO:  "+activityDetailDTO);
        if(null!=activityDetailDTO){
            logger.info("查询活动详细信息成功!! activityDetailDTO:{}",
                    JacksonUtil.writeValueAsString(activityDetailDTO));
            return activityDetailDTO;
        }
        //如果redis不存在,查表
        //活动表
        ActivityDO activityDO = activityMapper.selectById(activityId);
        //活动奖品表(没有奖品的基本信息)
        List<ActivityPrizeDO> activityPrizeDOList = activityPrizeMapper.selectByActivityId(activityId);
        //活动人员表(因为里面包含了人员的所有信息,所以不需要再查询人员表)
        List<ActivityUserDO> activityUserDOList = activityUserMapper.selectByActivityId(activityId);
        //奖品表(包含了奖品的所有信息)
        //先获取要查询的奖品id
        List<Long> prizeIds = activityPrizeDOList
                .stream()
                .map(ActivityPrizeDO::getActivityId)
                .toList();
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);
        //整合活动详细信息,存放redis
        ActivityDetailDTO detailDTO = convertToActivityDetailDTO(activityDO,activityUserDOList,prizeDOList,activityPrizeDOList);
        cacheActivity(detailDTO);
        //返回
        return detailDTO;
    }

    /**
     * 更新
     */
    @Override
    public void cacheActivity(Long activityId) {
        if(null==activityId){
            logger.warn("要缓存的活动id为空");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_IS_NULL);
        }

        //查询表数据,活动表 关联奖品 关联人员 奖品信息表
        ActivityDO activityDO = activityMapper.selectById(activityId);
        if(null==activityDO){
            logger.error("要缓存的活动id有误");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_IS_ERROR);
        }
        //活动奖品表(没有奖品的基本信息)
        List<ActivityPrizeDO> activityPrizeDOList = activityPrizeMapper.selectByActivityId(activityId);
        //活动人员表(因为里面包含了人员的所有信息,所以不需要再查询人员表)
        List<ActivityUserDO> activityUserDOList = activityUserMapper.selectByActivityId(activityId);
        //奖品表(包含了奖品的所有信息)
        //先获取要查询的奖品id
        List<Long> prizeIds = activityPrizeDOList
                .stream()
                .map(ActivityPrizeDO::getActivityId)
                .toList();
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);
        //整合活动详细信息,存放redis
        cacheActivity(convertToActivityDetailDTO(activityDO,activityUserDOList,prizeDOList,activityPrizeDOList));

    }

    /**
     * 缓存完整的活动信息 ActivityDetailDTO
     * @param activityDetailDTO
     */
    private void cacheActivity(ActivityDetailDTO activityDetailDTO) {
        //key: ACTIVITY_activityId  -->需要有一个前缀来表示使用环境
        //value: ActivityDetailDTO --> 要转换成一个json字符串
        if(null == activityDetailDTO||null==activityDetailDTO.getActivityId()){
            logger.warn("要缓存的活动信息不存在!");
            return;
        }
        System.out.println(JacksonUtil.writeValueAsString(activityDetailDTO));
        //缓存失败是否需要处理? --> 抽取端会有二次处理
        try {
            redisUtil.set(ACTIVITY_PREFIX+activityDetailDTO.getActivityId(),
                    JacksonUtil.writeValueAsString(activityDetailDTO),
                    ACTIVITY_TIMEOUT);//缓存完毕
        }catch (Exception e){
            logger.error("缓存活动异常. ActivityDetailDTO={}",JacksonUtil.writeValueAsString(activityDetailDTO));
        }

    }

    /**
     * 从缓存中根据活动id获取活动详细信息
     * @param activityId
     * @return ${RETURN_DESCRIPTION}
     */
    private ActivityDetailDTO getActivityFromCache(Long activityId){
        if(null==activityId){
            logger.warn("用于获取活动信息的key = activityId为空");
            return null;
        }
        try {
            String string = redisUtil.get(ACTIVITY_PREFIX+activityId);
            System.out.println("getActivityFromCache:   "+string);
            if(!StringUtils.hasText(string)){
                logger.info("获取的缓存数据为空!key = {}",ACTIVITY_PREFIX+activityId);
                return null;
            }
            return JacksonUtil.readValue(string,ActivityDetailDTO.class);
        }catch (Exception e){
            logger.error("从缓存中获取活动信息异常,key = {}",activityId,e);
            return null;
        }
    }

    /**
     * 根据基本的DO信息整合完整的活动信息
     * @param activityDO //活动的信息
     * @param activityUserDOList  //活动关联的人员信息
     * @param prizeDOList  //奖品的所有信息
     * @param activityPrizeDOList  //活动关联的奖品信息
     * @return  ActivityDetailDTO
     */
    private ActivityDetailDTO convertToActivityDetailDTO(ActivityDO activityDO,
                                            List<ActivityUserDO> activityUserDOList,
                                            List<PrizeDO> prizeDOList,
                                            List<ActivityPrizeDO> activityPrizeDOList) {
        ActivityDetailDTO activityDetailDTO = new ActivityDetailDTO();
        activityDetailDTO.setActivityId(activityDO.getId());
        activityDetailDTO.setActivityName(activityDO.getActivityName());
        activityDetailDTO.setDesc(activityDO.getDescription());
        activityDetailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));

        //奖品信息
        List<ActivityDetailDTO.PrizeDTO> prizeDTOList = activityPrizeDOList.stream()
                        .map(activityPrizeDO -> {
                            ActivityDetailDTO.PrizeDTO prizeDTO = new ActivityDetailDTO.PrizeDTO();
                            prizeDTO.setPrizeId(activityPrizeDO.getPrizeId());
                            //Optional 防止空指针异常
                            //使用流对象,同时在流对象里面进行遍历,找到对应值
                            Optional<PrizeDO> optionalPrizeDO = prizeDOList.stream()
                                    .filter(prizeDO ->prizeDO.getId().equals(activityPrizeDO.getPrizeId()))
                                    .findFirst();
                            //if prizeDO 为空不会执行这个方法,不为空才会执行
                            //用来写入信息,同时避免写if,避免空指针异常
                            optionalPrizeDO.ifPresent(prizeDO -> {
                                prizeDTO.setName(prizeDO.getName());
                                prizeDTO.setImageUrl(prizeDO.getImageUrl());
                                prizeDTO.setPrice(prizeDO.getPrice());
                                prizeDTO.setDescription(prizeDO.getDescription());
                            });
                            prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(activityPrizeDO.getPrizeTiers()));
                            prizeDTO.setPrizeAmount(activityPrizeDO.getPrizeAmount());
                            prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(activityPrizeDO.getStatus()));

                            return prizeDTO;
                        }).toList();
        activityDetailDTO.setPrizeDTOList(prizeDTOList);


        List<ActivityDetailDTO.UserDTO> userDTOList = activityUserDOList.stream()
                        .map(activityUserDO -> {
                            ActivityDetailDTO.UserDTO userDTO = new ActivityDetailDTO.UserDTO();
                            userDTO.setUserId(activityUserDO.getUserId());
                            userDTO.setUserName(activityUserDO.getUserName());
                            userDTO.setStatus(ActivityUserStatusEnum.forName(activityUserDO.getStatus()));
                            return userDTO;
                        }).collect(Collectors.toList());
        activityDetailDTO.setUserDTOList(userDTOList);
        return activityDetailDTO;
    }

    /**
     * 校验活动有效性,直接抛出异常
     * @param param
     */
    private void checkActivityInfo(CreateActivityParam param) {
        if(null==param){
            throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_EMPTY);
        }
        //人员id在人员表中是否存在
        List<Long> userIds = param.getActivityUserList()
                .stream()
                .map(CreateUserByActivityParam::getUserId)//这是一个映射,(对象,需要从这个对象里面获取信息::获取到的信息)
                .distinct()//去重
                .toList();
        //通过传进来的id去数据库中进行比对,将存在的id进行返回
        List<Long> existUserIds = userMapper.selectExistByIds(userIds);
        if(CollectionUtils.isEmpty(existUserIds)){
            logger.warn("existUserIds 为空");
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
        }
        userIds.forEach(id->{
            if(!existUserIds.contains(id)){
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
            }
        });

        //奖品id在奖品表中是否存在
        List<Long> prizeIds = param.getActivityPrizeList()
                .stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()//去重
                .toList();
        List<Long> existPrizeIds = prizeMapper.selectExistByIds(prizeIds);
        if(CollectionUtils.isEmpty(existPrizeIds)){
            logger.warn("existPrizeIds 为空");
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
        }
        prizeIds.forEach(id->{
            if(!existPrizeIds.contains(id)){
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
            }
        });

        //奖品数量<=人员数量
        int userAmount = param.getActivityUserList().size();
        long prizeAmount = param.getActivityPrizeList()
                .stream()
                .mapToLong(CreatePrizeByActivityParam::getPrizeAmount) //获得数量
                .sum();
        if ((userAmount<prizeAmount)){
            throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);
        }

        //校验活动奖品等奖有效性
        param.getActivityPrizeList().forEach(prize->{
           if(null == ActivityPrizeTiersEnum.forName(prize.getPrizeTiers())){
               throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);
           }
        });
    }
}
